window.util = window.util || {};

util.max = function(array) {
  var max = array[0];
  var len = array.length;
  for ( var i = 0; i < len; ++i) {
    if (array[i] > max) {
      max = array[i];
    }
  }
  return max;
};
var CALLBACK_DURATION = 150;  // unit: ms
var SMOOTHING_TIME_CONSTANT = 0.8999;
var NUM_SAMPLES = 2048;

var BackgroundMode = {
  ROOM : 'resources/room.wav',
  VALLEYS : 'resources/valleys.wav',
  LAUGHTER : 'resources/laugh.wav'
};

function SoundConverter(opt_analyserCallback, opt_jsProcessorCallback,
    opt_playbackRate, opt_bgMode) {
  this.context_ = new (window.AudioContext || window.webkitAudioContext)();
  this.playbackRate_ = opt_playbackRate || 1.0;
  this.sourceBuffer_ = null;
  this.grainWindow_ = null;
  this.analyser_ = null;
  this.jsProcessor_ = null;
  this.isBackgroundLoaded_ = false;
  this.compressor_ = null;
  this.convolver_ = null;
  this.currentNode_ = null;
  this.analyserTimer_ = null;
  this.timeoutId_ = null;

  var self_ = this;
  var grainWindowLength = 16384;

  this.grainWindow_ = new Float32Array(grainWindowLength);
  for ( var i = 0; i < grainWindowLength; i++)
    this.grainWindow_[i] = Math.sin(Math.PI * i / grainWindowLength);

  var initAnalyser = function(analyserCallback) {
    self_.analyser_ = self_.context_.createAnalyser();
    self_.analyser_.smoothingTimeConstant = SMOOTHING_TIME_CONSTANT;

    function process() {
      var freqByteData = new Uint8Array(self_.analyser_.frequencyBinCount);
      self_.analyser_.getByteTimeDomainData(freqByteData);
      var percent = util.max(freqByteData);
      analyserCallback(percent);
    }

    process();
    self_.analyserTimer_ = setInterval(process, CALLBACK_DURATION);
  };
  var initJsProcessor = function(jsProcessorCallback) {
    self_.jsProcessor_ = self_.context_.createJavaScriptNode(
        NUM_SAMPLES/* bufferSize */, 1/* num inputs */, 0 /* num outputs */);
    self_.jsProcessor_.onaudioprocess = function(e) {
      jsProcessorCallback(self_.context_.currentTime, e.inputBuffer.getChannelData(0));
    };
  };
  var initBackgroundMode = function(bgMode) {
    self_.compressor_ = self_.context_.createDynamicsCompressor();
    self_.convolver_ = self_.context_.createConvolver();
    self_.loadAudioStream(bgMode, function(buffer) {
      self_.isBackgroundLoaded_ = true;
      self_.convolver_.buffer = buffer;
    });
  };
  if (opt_analyserCallback) {
    initAnalyser(opt_analyserCallback);
  }

  if (opt_jsProcessorCallback) {
    initJsProcessor(opt_jsProcessorCallback);
  }

  if (opt_bgMode) {
    initBackgroundMode(opt_bgMode);
  }
};

SoundConverter.prototype.createSmoothNode = function(realTime, grainDuration) {
  var grainWindowNode = this.context_.createGainNode();
  grainWindowNode.gain.value = 0.0;
  // make default value 0
  grainWindowNode.gain.setValueCurveAtTime(this.grainWindow_, realTime,
      grainDuration / this.playbackRate_);
  this.currentNode_.connect(grainWindowNode);
  this.currentNode_ = grainWindowNode;
};

SoundConverter.prototype.loadAudioStream = function(url, successHandler) {
  function failureHandle(error) {
    alert(error);
  }

  var request = new XMLHttpRequest(), self = this;
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';

  request.onload = function() {
    var buffer = self.context_.createBuffer(request.response, false);
    successHandler(buffer);
  };

  request.onerror = function() {
    failureHandle('error loading:' + url);
  };

  request.send();
};

SoundConverter.prototype.playOnGrain = function() {
  if (!this.isBackgroundLoaded_ && this.convolver_) {
    return setTimeout(this.playOnGrain, 50);
  }

  var self_ = this;
  var realTime = 0.0;
  var grainTime = 0.0;
  var kGrainSize = 0.1;
  var grainDuration = kGrainSize;
  var grainSpacing = 0.5 * kGrainSize;
  var speed = 1.28;
  var bufferDuration = 58.0;
  if (!self_.context_) {
    return setTimeout(this.playOnGrain, 50);
  }
  realTime = self_.context_.currentTime;

  function schedulePlay() {
    var currentTime = self_.context_.currentTime;

    while (realTime < currentTime + 0.1) {

      var source = self_.context_.createBufferSource();
      source.buffer = self_.sourceBuffer_;
      source.playbackRate.value = self_.playbackRate_;

      self_.currentNode_ = source;

      self_.createSmoothNode(realTime, grainDuration);
      self_.createNoiseFilter();
      self_.createPanner();

      if (self_.convolver_) {
        self_.createBackgroundSound();
      }

      if (self_.analyser_) {
        self_.currentNode_.connect(self_.analyser_);
        self_.currentNode_ = self_.analyser_;
      }
      self_.currentNode_.connect(self_.context_.destination);

      if (self_.jsProcessor_) {
        source.connect(self_.jsProcessor_);
        self_.jsProcessor_.connect(self_.context_.destination);
      }

      source.noteGrainOn(realTime, grainTime, grainDuration);
      realTime += grainSpacing;
      grainTime += speed * grainSpacing;
      if (grainTime > bufferDuration)
        grainTime = 0.0;
      if (grainTime < 0.0)
        grainTime += bufferDuration;
      // backwards wrap-around
    }
  }

  schedulePlay();
  this.timeoutId_ = setInterval(schedulePlay, 25);
};

SoundConverter.prototype.createBackgroundSound = function() {
  var dryGainNode = this.context_.createGainNode();
  var wetGainNode = this.context_.createGainNode();
  wetGainNode.gain.value = 0.2;
  dryGainNode.gain.value = 1.0;

  this.currentNode_.connect(dryGainNode);
  this.currentNode_.connect(wetGainNode);
  dryGainNode.connect(this.compressor_);
  wetGainNode.connect(this.convolver_);
  this.convolver_.connect(this.compressor_);
  this.currentNode_ = this.compressor_;
};

SoundConverter.prototype.createNoiseFilter = function() {
  var lowFreqFilter = this.context_.createBiquadFilter();
  lowFreqFilter.type = lowFreqFilter.HIGHPASS;
  lowFreqFilter.frequency.value = 440;
  this.currentNode_.connect(lowFreqFilter);
  this.currentNode_ = lowFreqFilter;

  var highFreqGain = this.context_.createGainNode();
  highFreqGain.gain.value = 2.0;
  this.currentNode_.connect(highFreqGain);
  this.currentNode_ = highFreqGain;
};

SoundConverter.prototype.createPanner = function() {
  var pannerNode = this.context_.createPanner();
  pannerNode.panningModel = (window.AudioPannerNode || window.webkitAudioPannerNode).HRTF;
  // pannerNode.setPosition(0.6327, 1.2, 1.461);
  pannerNode.setPosition(0, 0, -2);

  this.currentNode_.connect(pannerNode);
  this.currentNode_ = pannerNode;
};

SoundConverter.prototype.stop = function() {
  clearInterval(this.analyserTimer_);
  clearInterval(this.timeoutId_);
};
